import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import * as puppeteer from 'puppeteer';
import axios from 'axios';
import {
  concatMap,
  defer,
  of,
  tap,
  map,
  catchError,
  timeout,
  delay,
  ReplaySubject,
  Observable,
  from,
  TimeoutError,
} from 'rxjs';
import IHelper from '@/services/helper.service';
import { IWonewSer } from '@/typing';
import IGlobal from '@/services/global.service';

@Injectable()
export class BiliBindService {
  authUri: string = 'https://member.bilibili.com/platform/home';

  prepare(accountId: number): Observable<IWonewSer.BindAccountTask> {
    const task: IWonewSer.BindAccountTask = {
      accountId,
      hash: IHelper.hash(24),
      startAt: Date.now(),
      state: 0,
    };
    IGlobal.bindAccountTaskSet[task.hash] = task;
    const webAjaxResponseSubject = new ReplaySubject<{type: "poll" | "generate", data: any}>()
    // @ts-ignore
    return defer(() => 
      puppeteer.launch({
        defaultViewport: {
          width: 1280,
          height: 720,
        },
        headless: true,
        // headless: false,
        args: ['--no-sandbox','--disable-setuid-sandbox'],
      }),
    ).pipe(
      tap((browser) => (task.browser = browser)),
      concatMap(() => task.browser.newPage()),
      tap((page) => (task.page = page)),
      concatMap(() => task.page.setRequestInterception(true)),
      map(() => {
        task.page.on('request', (interceptedRequest: puppeteer.HTTPRequest) => {
          if (interceptedRequest.isInterceptResolutionHandled()) return;
          if (
            interceptedRequest.url().endsWith('.png') ||
            interceptedRequest.url().endsWith('.jpg')
          ) {
            interceptedRequest.abort();
          } else {
            interceptedRequest.continue();
          }
        });
        task.page.on('response', async (res: puppeteer.HTTPResponse) => {
          const req: puppeteer.HTTPRequest = res.request()
          if (req.url().includes(`/passport-login/web/qrcode/poll`)) {
            let resJson = await res.json().catch(e => undefined)
            webAjaxResponseSubject.next({type: "poll", data: resJson})
            let stateCodeSet = {
              86101: 0, 86090: 1, 86038: 2, 
            }
            if (typeof stateCodeSet[resJson?.data?.code] === "number") {
              task.state = stateCodeSet[resJson?.data?.code]
            }
            let d1 = {
              code: 0,
              message: '0',
              ttl: 1,
              data: {
                url: '',
                refresh_token: '',
                timestamp: 0,
                code: 86101,
                message: '未扫码'
              }
            }
            let d2 = {
              code: 0,
              message: '0',
              ttl: 1,
              data: {
                url: '',
                refresh_token: '',
                timestamp: 0,
                code: 86090,
                message: '二维码已扫码未确认'
              }
            }

            let d3 =  {
              code: 0,
              message: '0',
              ttl: 1,
              data: {
                url: 'https://passport.biligame.com/x/passport-login/web/crossDomain?DedeUserID=404186078&DedeUserID__ckMd5=f3b1debd6c153fcf&Expires=1721449033&SESSDATA=5d606485,1721449033,e7724*11CjBTAWiy4puTxpG_UkW9oAZhDwngJyHWaup9uvYI6SeCRaJxGwwX-ww0BlkNp1YI6JUSVjZzQmVRbUY3dk02Z1dOeFAtUUpjdjF2TC1fZENkS0ZsZmUwZDVhQURBOGNHcFZxQnhlcUdLX0h4d0JQU29hR2c2Njk2dmUyRFRyXzhlVk1hN01vcFdBIIEC&bili_jct=994a2d29d083b192a612d02e1a176d43&gourl=https%3A%2F%2Fmember.bilibili.com%2F',
                refresh_token: 'f4f33cb27c2321ed540181ea3a92ac11',
                timestamp: 1705897033391,
                code: 0,
                message: ''
              }
            }

            let d4 =  {
              "url": "",
              "refresh_token": "",
              "timestamp": 0,
              "code": 86038,
              "message": "二维码已失效"
            }
            // console.log("/passport-login/web/qrcode/poll", resJson)
          }
          if (req.url().includes(`/passport-login/web/qrcode/generate`)) {
            let resJson = await res.json().catch(e => undefined)
            webAjaxResponseSubject.next({type: "generate", data: resJson})
            let d1 = {
              code: 0,
              message: '0',
              ttl: 1,
              data: {
                url: 'https://passport.bilibili.com/h5-app/passport/login/scan?navhide=1&qrcode_key=9548230e5c0825869bd59be3331c1d25&from=main_web',
                qrcode_key: '9548230e5c0825869bd59be3331c1d25'
              }
            }
          }
        });
      }),
      concatMap(() => task.page.goto(this.authUri)),
      concatMap(() => {
        return new Observable<string>((observer => {
          webAjaxResponseSubject.subscribe(
            res => {
              if (res.type === "generate" && res.data?.data?.url) {
                observer.next(res.data?.data?.url)
                observer.complete()
              }
            }
          )
        }))
      }),
      tap(targeUri => task.targetUri = targeUri),
      map(() => task),
      timeout(15000),
      catchError(error => {
        task.browser?.close().catch(e => e)
        throw error
      })
    );
  }

  check(hash: string): Observable<IWonewSer.BindAccountTask> {
    return of(IGlobal.bindAccountTaskSet[hash]).pipe(
      map(task => {
        if (!task || task.state === 2) {
          throw new HttpException(`Hash expired.`, -2000)
        }
        return task
      }),
      concatMap(task => {
        if (task.page.url() !== this.authUri) {
          return of(task)
        }
        return from(task.page.cookies()).pipe(
          map(cookies => {
            task.browser.close().catch(e => e)
            task.cookie = JSON.stringify(cookies)
            IGlobal.bindAccountTaskSet[hash] = undefined
            return task
          }),
          concatMap(task => {
            return this.checkCookie(task.cookie).pipe(
              map(({platformUser}) => {
                if (!platformUser) {
                  throw new HttpException(`Get platform user by cookie failed.`, -2001)
                }
                task.state = 3
                task.platformUser = platformUser
                return task
              })
            )
          })
        ) 
      })
    )
  }

  checkCookie(cookie: string): Observable<{valid: boolean, platformUser?: any, cookie?: string}> {
    return defer(async () => {
      let browser: puppeteer.Browser | undefined = undefined
      try {
        browser = await puppeteer.launch({
          headless: true,
          args: ['--no-sandbox','--disable-setuid-sandbox'],
        })
        const page = await browser.newPage()
        const cookies = JSON.parse(cookie) as puppeteer.Cookie[]
        for(let i = 0; i < cookies.length; i++){
          await page.setCookie(cookies[i])
        }
        await page.goto("https://member.bilibili.com/platform/upload/video/frame")
        const userInfoRes = await page.waitForResponse(res => res.url().includes("web/archive/pre"))
        const userInfoResJson = await userInfoRes.json()
        const updatedCookies = JSON.stringify(await page.cookies())
        await browser.close().catch(e => e)
        if (userInfoResJson.code === 0) {
          return {valid: true, platformUser: userInfoResJson.data.myinfo, cookie: updatedCookies}
        }
        return {valid: false, platformUser: null, cookie: ""}
      } catch(e) {
        browser && browser.close().catch(e => e)
        throw new HttpException("Check bili cookie failed.", HttpStatus.BAD_REQUEST)
      }
    })
  }
}
